﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestListGenerator.Attributes;

namespace TestListGenerator
{
    public class ReflectionHelper
    {
        /// <summary>
        /// Checks to see if a particular method is a test or not (based on the TestMethod attribute)
        /// </summary>
        /// <param name="mi">The reflected information about the method</param>
        /// <returns>true if the method is a test method, false otherwise</returns>
        public static bool MethodIsATest(MethodInfo mi) 
        {
            if (mi == null)
            {
                throw new ArgumentNullException("mi", "You need to pass in MethodInfo information in order to see if the method is a test or not");
            }
            return (mi.GetCustomAttributes(typeof(TestMethodAttribute), true).Length > 0);
        }

        /// <summary>
        /// Gets a list of the Test Categories associated to a test, via the TestProperty / TestCategory attributes
        /// </summary>
        /// <param name="mi">The method to retreive the categories for</param>
        /// <returns>A list of Categories associated with the Test method</returns>
        public static HashSet<string> GetTestCategories(MethodInfo mi)
        {
            if (mi == null)
            {
                throw new ArgumentNullException("You need to pass in a MethodInfo in order to see if it has any assigned TestCategories");
            }
            HashSet<string> result = new HashSet<string>();

            // Cycle through the attributes, checking to see if any of them are categories, and 
            // if so, adding the values to the list to return
            foreach (TestPropertyAttribute attribute in mi.GetCustomAttributes(typeof(TestPropertyAttribute), true))
            {
                if (attribute.Name.ToLower() == "category")
                {
                    // don't want to be case sensitive
                    result.Add(attribute.Value.ToLower());
                }
            }

            // Now cycle through our custom attribute, TestCategoryAttribute and add
            // any attributes
            foreach (TestCategoryAttribute attribute in mi.GetCustomAttributes(typeof(TestCategoryAttribute), true))
            {
                result.Add(attribute.Category.ToLower());
            }

            return result;
        }

        /// <summary>
        /// This method reflects an assembly and returns a dictionary mapping the tests to the 
        /// Categories they have been setup with
        /// </summary>
        /// <param name="assemblyPath">The assembly to load</param>
        /// <returns>A Dictionary mapping the names of tests to the categories associated via TestProperty / TestCategory</returns>
        public static Dictionary<MethodInfo, HashSet<string>> GetTestsFromAssembly(string assemblyPath) 
        {
            // Check for nulls
            if (assemblyPath == null)
            {
                throw new ArgumentNullException("assemblyPath", "You need to pass in the path to the assembly you want to load");
            }

            Dictionary<MethodInfo, HashSet<string>> result = new Dictionary<MethodInfo, HashSet<string>>();

            // Load in the assembly
            Assembly asm = Assembly.LoadFrom(assemblyPath);

            // Get a list of types
            Type[] types = asm.GetExportedTypes();

            // Cycle through the types, looking for tests
            foreach (Type type in types)
            {
                // Only search public instance methods
                foreach (MethodInfo mi in type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
                {
                    // Check to see if it's a test
                    if (ReflectionHelper.MethodIsATest(mi))
                    {
                        result[mi] = GetTestCategories(mi);   
                    }
                }
            }

            return result;
        }
    }
}
